package model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;

public class SignatureMemory {
	
	private final HashMap<UUID, WIFIsignature> signatureMemory;
	private final long timeTolerance;
	
	public SignatureMemory(long timeTolerance){
		signatureMemory = new HashMap<UUID, WIFIsignature>();
		this.timeTolerance = timeTolerance;
	}
	public synchronized void reportSignalSignature(WIFIsignature sig){
		UUID devUuid = sig.getDeviceUUID();
		signatureMemory.put(devUuid, sig);
	}
	
	public synchronized ArrayList<TempUUIDdistance> getClosestDevs(int numOfDevs, UUID myUUID){
		
		ArrayList<UUID> uuidKeys = getMemoryUUIDs();
		SignalComparator comparator = new SignalComparator();
		UUIDdistancecomparator uuidDistanceComparator = new UUIDdistancecomparator();
		ArrayList<String> intersectionMAClistMy = new ArrayList<String>();
		ArrayList<TempUUIDdistance> UUIDdistances = new ArrayList<TempUUIDdistance>();
		
		for(int i = 0; i < signatureMemory.size(); i++){
			int numOfIntersectionNodes = 0;
			UUID otherUUID = uuidKeys.get(i);
			
			if(myUUID.compareTo(otherUUID) == 0){
				continue;
			}
			if(signatureMemory.get(otherUUID).getTime().getTime() - System.currentTimeMillis() > timeTolerance){
				continue;
			}
			intersectionMAClistMy = findIntersectionSignal(myUUID, otherUUID);

			numOfIntersectionNodes = intersectionMAClistMy.size();
			Integer[] myRanks = createRankArray(numOfIntersectionNodes);
			Integer[] otherRanks = createRankArray(numOfIntersectionNodes);
			
			comparator.setUUID(myUUID, intersectionMAClistMy);
			Arrays.sort(myRanks, comparator);
			
			comparator.setUUID(otherUUID, intersectionMAClistMy);
			Arrays.sort(otherRanks, comparator);
//			

			double spearmanRankOrder = SpearmanRankOrder(myRanks, otherRanks);
			double estimatedDistance = estimateDistance(numOfIntersectionNodes, spearmanRankOrder);
			UUIDdistances.add(new TempUUIDdistance(otherUUID, estimatedDistance));
			Collections.sort(UUIDdistances, uuidDistanceComparator);
			
		}
		return UUIDdistances;	
		
	}
	
	private double SpearmanRankOrder(Integer[] myRanks, Integer[] otherRanks){
		double avgRank = (myRanks.length*(myRanks.length + 1))/2;
		avgRank /= myRanks.length;
		
		Integer[] myIndexes = new Integer[myRanks.length];
		Integer[] otherIndexes = new Integer[otherRanks.length];
		
		for(int i = 0; i < myRanks.length; i++){
			myIndexes[myRanks[i]] = i;
			otherIndexes[otherRanks[i]] = i;
		}
		
		double brojnik = 0;
		double nazivnik1 = 0;
		double nazivnik2 = 0;
		for(int i = 0; i < myRanks.length; i++){
			brojnik += (myIndexes[i] - avgRank)*(otherIndexes[i] - avgRank);
			nazivnik1 += (myIndexes[i] - avgRank)*(myIndexes[i] - avgRank);
			nazivnik2 += (otherIndexes[i] - avgRank)*(otherIndexes[i] - avgRank);
		}
		
		nazivnik1 = Math.sqrt(nazivnik1);
		nazivnik2 = Math.sqrt(nazivnik2);
		
		return brojnik/(nazivnik1*nazivnik2);
	}
	
	private ArrayList<String> findIntersectionSignal(UUID myUUID, UUID otherUUID){
		WIFIsignature mySignature = signatureMemory.get(myUUID);
		WIFIsignature otherSignature = signatureMemory.get(otherUUID);
		
		HashMap<String, Integer> mySignals = mySignature.getSignalSignature();
		HashMap<String, Integer> otherSignals = otherSignature.getSignalSignature();
		
		ArrayList<String> macList = new ArrayList<String>();
		
		if(mySignals.size() < otherSignals.size()){
			macList = getMACadress(mySignals);
			for(int i = 0; i < macList.size(); i++){
				if(otherSignals.get(macList.get(i)) == null){
					macList.remove(i);
				}
			}
		}else{
			macList = getMACadress(otherSignals);
			for(int i = 0; i < macList.size(); i++){
				if(mySignals.get(macList.get(i)) == null){
					macList.remove(i);
				}
			}
		}
		return macList;
	}
	
	private ArrayList<UUID> getMemoryUUIDs(){
		ArrayList<UUID> outputList = new ArrayList<UUID>();
		Set<UUID> set = signatureMemory.keySet();
		for (UUID u : set){
			outputList.add(u);
		}
		return outputList;
	}
	
	private ArrayList<String> getMACadress(HashMap<String, Integer> signal){
		ArrayList<String> outputList = new ArrayList<String>();
		Set<String> set = signal.keySet();
		for (String u : set){
			outputList.add(u);
		}
		return outputList;
	}
	
	private Integer[] createRankArray(int size){
		Integer[] array = new Integer[size];
		for(int i = 0; i < size; i++){
			array[i] = i;
		}
		return array;
		
	}
	
	private double estimateDistance(double numOfSameAPs, double spearmanRankOrder){
		return -2.53*numOfSameAPs -2.9*spearmanRankOrder + 22.31;
	}
	
	public class SignalComparator implements Comparator<Integer> {
		private HashMap<String, Integer> signals;
		private ArrayList<String> array;
		
		public void setUUID(UUID refUuid, ArrayList<String> array){
			WIFIsignature signature = signatureMemory.get(refUuid);
			signals = signature.getSignalSignature();
			this.array = array;
		}
		
	    @Override
	    public int compare(Integer o1, Integer o2) {
	        return signals.get(array.get(o1)) - signals.get(array.get(o1));
	    }
	}
	
	public class UUIDdistancecomparator implements Comparator<TempUUIDdistance>{
		@Override
		public int compare(TempUUIDdistance arg0, TempUUIDdistance arg1) {
			
			return (int)(arg0.getDistance() - arg1.getDistance());
		}
	}
	
}
